﻿
using System;
using System.Drawing;
using GECS.GraphicsUtil;
using GECS.Core;
using GECS.Util;
using GECS.Input;

namespace GECS.Object {

	/// <summary>
	/// This class represents objects in a game. Any object must extend this class.
	/// The events are automatically managed if this object is dynamically loaded
	/// through a map loader. Else you have to detect them on your own. This is an
	/// example object.
	/// </summary>
	/// <example>
	/// <code>
	/// public class MyObject : GObject
	/// {
	/// 
	///     public MyObject (float x, float y) : base (x, y)
	///     {
	///         Image = Game.LoadImage(&quot;&lt;image&gt;&quot;);
	///     }
	/// 
	///     public override void Update(long elapsedTime)
	///     {
	///         // Update this object
	///     }
	/// 
	///     public override void Collision(GObject other)
	///     {
	///         // Another object has been collided.
	///         if (other is MyObject2)
	///         {
	///             // other is instance of MyObject2
	///         }
	///     }
	/// 
	/// }
	/// </code>
	/// </example>
    public class GObject {

        // The animation object
        private Animation anim;
        // The positions and velocities
        private float x;
        private float y;
        private float dx;
        private float dy;
        private float oldX;
        private float oldY;

        /// <summary>
        /// The depth of this object
        /// </summary>
        protected int depth = 0;

        /// <summary>
        /// Is this object visible??
        /// </summary>
        protected bool visible = true;

        // If this object is solid and alive
        private bool solid = false;
        private bool alive = true;

        // Is this object a collision listener???
        private bool collision_listener = true;

        // The bounds
        private Rectangle bounds;

        /// <summary>
        /// Constructs an object which is invisible.
        /// </summary>
        public GObject() {}

        /// <summary>
        /// Constructs an invisible object at a position x,y.
        /// </summary>
        /// <param name="x">The x-position.</param>
        /// <param name="y">The y-position.</param>
        public GObject(float x, float y) {
            X = x;
            Y = y;
        }

        /// <summary>
        /// Constructs an object with an image at a position x,y.
        /// </summary>
        /// <param name="img">The image of the object.</param>
        /// <param name="x">The x-position.</param>
        /// <param name="y">The y-position.</param>
        public GObject(Image img, float x, float y) {
            Image = img;
            X = x;
            Y = y;
        }

        /// <summary>
        /// Constructs an object with an animation at a position x,y.
        /// </summary>
        /// <param name="anim">The animation of this object.</param>
        /// <param name="x">The x-position.</param>
        /// <param name="y">The y-position.</param>
        public GObject(Animation anim, float x, float y) {
            Animation = anim;
            X = x;
            Y = y;
        }

        /// <summary>
        /// Constructs an object with an animation object.
        /// </summary>
        /// <param name="anim">The animation of this object.</param>
        public GObject(Animation anim) {
            Animation = anim;
        }

        /// <summary>
        /// Constructs an object with an image object.
        /// </summary>
        /// <param name="img">The image of this object.</param>
        public GObject(Image img) {
            Image = img;
        }

        /// <summary>
        /// Clones this object. Creates a copy which does not respond to events.
        /// </summary>
        /// <returns>A shallow copy of the object.</returns>
        public GObject Clone() {
            GObject obj = new GObject();
            obj.X = X;
            obj.Y = Y;
            obj.Animation = Animation.Clone();
            obj.VelocityX = VelocityX;
            obj.VelocityY = VelocityY;
            obj.Depth = Depth;
            return null;
        }

        /// <summary>
        /// Update this object.
        /// </summary>
        /// <param name="elapsedTime">The time elapsed in the previous frame.</param>
        public virtual void Update(long elapsedTime) {
        }

        /// <summary>
        /// Updates this object and it's animation.
        /// </summary>
        /// <param name="elapsedTime">The rime taken by previous frame.</param>
        public void SuperUpdate(long elapsedTime) {
            Update(elapsedTime);
            anim.Update(elapsedTime);
        }

        /// <summary>
        /// Moves this object.
        /// </summary>
        public void Move() {
            MoveHorizontally();
            MoveVertically();
        }

        /// <summary>
        /// Moves this object horizontally.
        /// </summary>
        public void MoveHorizontally() {
            float nx = x + dx;
            if (Check(nx, Y)) {
                X = nx;
            }
        }

        /// <summary>
        /// Moves this object vertically.
        /// </summary>
        public void MoveVertically() {
            float ny = y + dy;
            if (Check(X, ny)) {
                Y = ny;
            }
        }

        /// <summary>
        /// Gets or sets the depth of this object.
        /// </summary>
        public int Depth {
            get {
                return depth;
            } set {
                depth = value;
            }
        }

        /// <summary>
        /// Could be used to give limits to the object.
        /// </summary>
        /// <param name="nx">The new x-position.</param>
        /// <param name="ny">The new y-position.</param>
        /// <returns>True if can move, else false.</returns>
        public bool Check(float nx, float ny) {
            return true;
        }

        /// <summary>
        /// Returns true if this object has been clicked by a mouse button.
        /// </summary>
        /// <returns>True if clicked, else false.</returns>
        public bool IsClicked() {
            if (GMouse.IsClicked() && Bounds.Contains(GMouse.X, GMouse.Y)) {
				return true;
            }
            return false;
        }

        /// <summary>
        /// Returns the boundaries of this object.
        /// </summary>
        public Rectangle Bounds {
            get {
                bounds.X = (int)x;
                bounds.Y = (int)y;
                bounds.Width = Width;
                bounds.Height = Height;
                return bounds;
            }
        }

        /// <summary>
        /// Checks if this object is colliding the other object. You can enable
        /// pixel-perfect collision detection by using
        /// <code>
        /// Global.USE_PIXELPERFECT_COLLISION = true;
        /// </code>
        /// </summary>
        /// <param name="other">The other object.</param>
        /// <returns>True if a collision has been found.</returns>
        public bool IsCollidingWith(GObject other) {
            if (this == other) {
                return false;
            }
            if (!IsAlive() || !other.IsAlive()) {
                return false;
            }
            int x1 = (int)x;
            int y1 = (int)y;
            int x2 = (int)other.X;
            int y2 = (int)other.Y;
            bool boolean = false;
            if (x1 < x2 + other.Width) {
                if (x2 < x1 + Width) {
                    if (y1 < y2 + other.Height) {
                        if (y2 < y1 + Height) {
                            boolean = true;
                        }
                    }
                }
            }
            if (boolean && Global.USE_PIXELPERFECT_COLLISION) {
                boolean = GUtil.IsPixelPerfectCollision(x, y, Image, other.X, other.Y, other.Image);
            }
                      return boolean;
        }

        /// <summary>
        /// Automatically aligns this object with other.
        /// </summary>
        /// <param name="other">The other object.</param>
        public void AlignWith(GObject other) {
            // Get the intersection rectangle
            Rectangle i = Rectangle.Intersect(Bounds, other.Bounds);
            if (i.Width > i.Height) {
                // A vertical collision
                if (Y < other.Y ) {
                    // We're on top of other
                    Y = other.Y - Height;
                } else if (Y > other.Y) {
                    // We're below other
                    Y = other.Y + other.Height;
                }
            } else {
                // A horizontal collision
                if (X < other.X) {
                    // We're left to other
                    X = other.X - Width;
                } else if (X > other.X) {
                    // We're right to other
                    X = other.X + other.Width;
                }
            }
        }

        /// <summary>
        /// Gets or sets the visibilty of this object.
        /// </summary>
        public bool Visible {
            get {
                return visible;
            } set {
                visible = value;
            }
        }

        /// <summary>
        /// Is this object alive?
        /// </summary>
        /// <returns>True if alive, else false.</returns>
        public bool IsAlive() {
            return alive;
        }

        /// <summary>
        /// Destroys this object.
        /// </summary>
        public void Destroy() {
            alive = false;
        }

        /// <summary>
        /// Checks if an object has collided this on top.
        /// </summary>
        /// <param name="other">The other object.</param>
        /// <returns>True if collision on top.</returns>
        public bool IsTopCollision(GObject other) {
            bool boolean = IsCollidingWith(other);
            if (boolean) {
                if (Y >= other.Y) {
                    boolean = true;
                } else {
                    boolean = false;
                }
            }
            return boolean;
        }

        /// <summary>
        /// Checks if an object has collided this on bottom.
        /// </summary>
        /// <param name="other">The other object.</param>
        /// <returns>True if bottom wise collision.</returns>
        public bool IsBottomCollision(GObject other) {
            bool boolean = IsCollidingWith(other);
            if (boolean) {
                if (Y <= other.Y) {
                    boolean = true;
                } else {
                    boolean = false;
                }
            }
            return boolean;
        }

        /// <summary>
        /// Checks if an object has collided this on left.
        /// </summary>
        /// <param name="other">The other object.</param>
        /// <returns>True if collision on left.</returns>
        public bool IsLeftCollision(GObject other) {
            bool boolean = IsCollidingWith(other);
            if (boolean) {
                if (X >= other.X) {
                    boolean = true;
                } else {
                    boolean = false;
                }
            }
            return boolean;
        }

        /// <summary>
        /// Checks if an object has collided this from the right.
        /// </summary>
        /// <param name="other">The other object.</param>
        /// <returns>True if right collision.</returns>
        public bool IsRightCollision(GObject other) {
            bool boolean = IsCollidingWith(other);
            if (boolean) {
                if (X <= other.X) {
                    boolean = true;
                } else {
                    boolean = false;
                }
            }
            return boolean;
        }

        /// <summary>
        /// Called by the map automatically if a collision event occurs.
        /// Don't call the base method.
        /// </summary>
        /// <param name="other">The object wich had been collided.</param>
        public virtual void Collision(GObject other) {
            collision_listener = false;
        }

        /// <summary>
        /// Moves this object to a specified point with a specific speed. Note that
        /// the velocity used is independent of vertical or horizontal velocities of
        /// this object.
        /// </summary>
        /// <param name="nx">The new x-position.</param>
        /// <param name="ny">The new y-position.</param>
        /// <param name="speed">The speed with which to move.</param>
        /// <returns>True if the new point has been reached.</returns>
        public bool MoveTo(float nx, float ny, float speed) {
            bool _x = false;
            bool _y = false;
            int distance = (int) Math.Sqrt((double) ((x - nx) * (x - nx) + (y - ny)
                                           * (y - ny)));
            float vel = Math.Min(distance, speed);
            float newx = x;
            float newy = y;
            if (x > nx) {
                // We should move left
                newx -= vel;
            } else if (x < nx) {
                // We should move right
                newx += vel;
            } else {
                _x = true;
            }
            if (y > ny) {
                // We should move up
                newy -= vel;
            } else if (y < ny) {
                // We should move down
                newy += vel;
            } else {
                _y = true;
            }
            if (Check(newx, newy)) {
                x = newx;
                y = newy;
            }
            return (_x && _y);
        }

        /// <summary>
        /// Moves this object to a specified point with a specific speed. Note that
        /// the velocity used is independent of vertical or horizontal velocities of
        /// this object.
        /// </summary>
        /// <param name="nx">The new x-position.</param>
        /// <param name="ny">The new y-position.</param>
        /// <param name="speed">The speed with which to move.</param>
        /// <returns>True if the new point has been reached.</returns>
        public bool MoveTo(int nx, int ny, float speed) {
            return MoveTo((float) nx, (float) ny, speed);
        }

        /// <summary>
        /// This object bounces back from the other object in a natural way. Please
        /// realize that the bounce is not completely accurate because this depends
        /// on many properties. But in many situations the effect is good enough. Had
        /// some bugs in pixel perfect detection mode if the image has a larger area
        /// of complete alpha. If using PPCD, make the object fit the image size by
        /// removing the alpha and resizing the image.
        /// </summary>
        /// <param name="other">The other object.</param>
        public void Bounce(GObject other) {
            int xd = (int) ((other.x + other.Width / 2) - (x + Width / 2));
            int yd = (int) ((other.y + other.Height / 2) - (y + Height / 2));
            if (xd < 0) {
                xd = -xd;
            }
            if (yd < 0) {
                yd = -yd;
            }
            if (xd > yd) {
                dx = -dx;
            } else {
                dy = -dy;
            }
        }

        /// <summary>
        /// This object is aligned next to an object.
        /// </summary>
        /// <param name="other">The other object.</param>
        public void AlignNextTo(GObject other) {
            int xd = (int) ((other.x + other.Width / 2) - (x + Width / 2));
            int yd = (int) ((other.y + other.Height / 2) - (y + Height / 2));
            if (xd < 0) {
                xd = -xd;
            }
            if (yd < 0) {
                yd = -yd;
            }
            if (xd > yd) {
                AlignHorizontallyTo(other);
            } else {
                AlignVerticallyTo(other);
            }
        }

        /// <summary>
        /// Align this object horizontally with other
        /// </summary>
        /// <param name="other">The other object.</param>
        public void AlignHorizontallyTo(GObject other) {
            if (X > other.X) {
                X = other.X + other.Width;
            } else if (X < other.X) {
                X = other.X - Width;
            }
        }

        /// <summary>
        /// Align this object vertically with other
        /// </summary>
        /// <param name="other">The other object.</param>
        public void AlignVerticallyTo(GObject other) {
            if (Y > other.Y) {
                Y = other.Y + other.Height;
            } else if (Y < other.Y) {
                Y = other.Y - Height;
            }
        }

        /// <summary>
        /// Predicts the next x-position based on the velocity
        /// </summary>
        public float GetNextX() {
            return x + dx;
        }

        /// <summary>
        /// Predicts the next y-position based on the velocity
        /// </summary>
        public float GetNextY() {
            return y + dy;
        }

        /// <summary>
        /// Moves this object to the next point which on next move would collide.
        /// Works perfectly only in the pixel perfect collision detection mode. This
        /// automatically resets the velocities.
        /// </summary>
        /// <param name="other">The other object.</param>
        public void MoveToContact(GObject other) {
            MoveToContact(other, true, true);
        }
        
        /// <summary>
        /// Moves this object to the next point which on next move would collide.
        /// Works perfectly only in the pixel perfect collision detection mode. This
        /// automatically resets the velocities.
        /// </summary>
        /// <param name="other">The other object.</param>
        /// <param name="horizontal">Should move horizontally?</param>
        /// <param name="vertical">Should move vertically?</param>
        public void MoveToContact(GObject other, bool horizontal, bool vertical) {
            if (horizontal) {
                if (IsLeftCollision(other)) {
                    X = other.X + other.Width;
                    while (!IsCollidingWith(other)) {
                        X = X-1;
                    }
                } else if (IsRightCollision(other)) {
                    X = other.X - Width;
                    while (!IsCollidingWith(other)) {
                        X = X+1;
                    }
                }
                VelocityX = 0;
            }
            if (vertical) {
                if (IsTopCollision(other)) {
                    Y = other.Y + other.Height;
                    while (!IsCollidingWith(other)) {
                        Y = Y-1;
                    }
                } else if (IsBottomCollision(other)) {
                    Y = other.Y - Height;
                    while (!IsCollidingWith(other)) {
                        Y = Y+1;
                    }
                }
                VelocityY = 0;
            }
        }

        /// <summary>
        /// Gets or sets the solid state of the object.
        /// </summary>
        public bool Solid {
            get {
                return solid;
            } set {
                solid = value;
            }
        }

        /// <summary>
        /// Gets or sets the horizontal position of the object.
        /// </summary>
        public float X {
            get {
                return x;
            } set {
                oldX = x;
                x = value;
            }
        }

        /// <summary>
        /// Gets or sets the vertical position of the object.
        /// </summary>
        public float Y {
            get {
                return y;
            } set {
                oldY = y;
                y = value;
            }
        }

        /// <returns>The old x-position of this object.</returns>
        public float GetOldX() {
            return oldX;
        }
        
        /// <returns>The old y-position of this object.</returns>
        public float GetOldY() {
            return oldY;
        }

        /// <summary>
        /// Gets you the width of this object.
        /// </summary>
        public int Width {
            get {
                return anim.GetImage().Width;
            }
        }

        /// <summary>
        /// Gets you the height of this object.
        /// </summary>
        public int Height {
            get {
                return anim.GetImage().Height;
            }
        }

        /// <summary>
        /// Gets or sets the horizontal velocity.
        /// </summary>
        public float VelocityX {
            get {
                return dx;
            } set {
                dx = value;
            }
        }

        /// <summary>
        /// Gets or sets the vertical velocity.
        /// </summary>
        public float VelocityY {
            get {
                return dy;
            } set {
                dy = value;
            }
        }

        /// <summary>
        /// Gets or sets the animation.
        /// </summary>
        public Animation Animation {
            get {
                return anim;
            } set {
                anim = value;
            }
        }

        /// <summary>
        /// Gets or sets the image.
        /// </summary>
        public Image Image {
            get {
                return anim.GetImage();
            } set {
                anim = new Animation(new Image[] { value }, 150);
            }
        }

        /// <summary>
        /// Is the Collision() method implemented on this method?
        /// </summary>
        /// <returns>True if implemented, else false.</returns>
        public bool IsCollisionListener() {
            return collision_listener;
        }

        /// <summary>
        /// Render's this object onto the graphics context.
        /// </summary>
        /// <param name="g">The graphics context.</param>
        public void Render(Graphics g) {
            g.DrawImage(anim.GetImage(), x, y, Width, Height);
        }

    }

}